#include "configure.h"
#include "attr.h"
#include "esp_log.h"

static const char *GATTS_TAG = "GATTS_CONFIGURE";

struct configure configure;

// set attr_num service_num char_num cccd_num
static bool set_num_var(struct gatts_profile *profile,
			struct configure *configure)
{
	int i, j;
	int attr_num = 0;
	int char_num = 0;
	int cccd_num = 0;

	for (i = 0; i < profile->service_num; i++) {
		struct gatts_service *s = &profile->services[i];

		attr_num++;
		char_num += s->char_num;

		for (j = 0; j < s->char_num; j++) {
			struct gatts_char *c = &s->characteristics[j];

			attr_num += 2;

			if (!(c->perm &
			      (GATTS_PERM_NOTIFY | GATTS_PERM_INDICATE)))
				continue;

			attr_num++;
			cccd_num++;
		}
	}

	configure->attr_num = attr_num;
	configure->service_num = profile->service_num;
	configure->char_num = char_num;
	configure->cccd_num = cccd_num;

	return true;
}

// set attr_db service_idx char_val_idx char_uuid cccd_idx char_cccd_uuid
static bool set_pointer_var(struct gatts_profile *profile,
			    struct configure *configure)
{
	if (!(configure->attr_db || configure->service_idx ||
	      configure->char_val_idx || configure->char_uuid ||
	      configure->cccd_idx || configure->char_cccd_uuid))
		return false;

	int i, j;
	int cnt = 0;
	int _service_idx = 0;
	int _char_idx = 0;
	int _cccd_idx = 0;

	for (i = 0; i < profile->service_num; i++) {
		struct gatts_service *s = &profile->services[i];

		set_service_declaration(&configure->attr_db[cnt], s);

		// set service_idx
		configure->service_idx[_service_idx] = cnt;
		_service_idx++;

		for (j = 0; j < s->char_num; j++) {
			struct gatts_char *c = &s->characteristics[j];

			cnt++;
			set_char_declaration(&configure->attr_db[cnt], c);

			cnt++;
			set_char_value(&configure->attr_db[cnt], c);

			// set char_val_idx
			configure->char_val_idx[_char_idx] = cnt;
			// set char_uuid
			configure->char_uuid[_char_idx] = &c->uuid;
			_char_idx++;

			if (!(c->perm &
			      (GATTS_PERM_NOTIFY | GATTS_PERM_INDICATE)))
				continue;

			cnt++;
			// set cccd
			set_cccd(&configure->attr_db[cnt], c);

			// set cccd_idx
			configure->cccd_idx[_cccd_idx] = cnt;
			// set char_cccd_uuid
			configure->char_cccd_uuid[_cccd_idx] = &c->uuid;
			_cccd_idx++;
		}
		cnt++;
	}

	configure->profile = profile;

	return true;
}

void gatts_configure(struct gatts_profile *profile)
{
	configure.gatts_if =
		ESP_GATT_IF_NONE; /* Not get the gatt_if, so initial is
                                             ESP_GATT_IF_NONE */

	configure.device_name = profile->dev_name;
	ESP_LOGI(GATTS_TAG, "device_name: %s", configure.device_name);
	// set attr_num service_num char_num cccd_num
	set_num_var(profile, &configure);

	configure.handle_list =
		(uint16_t *)calloc(configure.attr_num, sizeof(uint16_t));

	configure.attr_db = (esp_gatts_attr_db_t *)calloc(
		configure.attr_num, sizeof(esp_gatts_attr_db_t));

	configure.service_idx =
		(uint8_t *)calloc(configure.service_num, sizeof(uint8_t));

	configure.char_val_idx =
		(uint8_t *)calloc(configure.char_num, sizeof(uint8_t));

	configure.cccd_idx =
		(uint8_t *)calloc(configure.cccd_num, sizeof(uint8_t));

	configure.char_uuid = (struct gatts_uuid **)calloc(
		configure.char_num, sizeof(struct gatts_uuid *));

	configure.char_cccd_uuid = (struct gatts_uuid **)calloc(
		configure.cccd_num, sizeof(struct gatts_uuid *));

	// set profile attr_db service_idx char_val_idx char_uuid cccd_idx char_cccd_uuid
	if (!(set_pointer_var(profile, &configure))) {
		ESP_LOGE(GATTS_TAG, "configure malloc error");
	}

	for (int i = 0; i < configure.attr_num; i++) {
		print_attr(&configure.attr_db[i]);
	}
}

void gatts_unconfigure()
{
	free(configure.handle_list);
	configure.handle_list = NULL;

	free(configure.attr_db);
	configure.attr_db = NULL;

	free(configure.service_idx);
	configure.service_idx = NULL;

	free(configure.char_val_idx);
	configure.char_val_idx = NULL;

	free(configure.cccd_idx);
	configure.cccd_idx = NULL;

	free(configure.char_uuid);
	configure.char_uuid = NULL;

	free(configure.char_cccd_uuid);
	configure.char_cccd_uuid = NULL;
}

int get_idx_by_handle(uint16_t handle)
{
	for (int i = 0; i < configure.attr_num; i++) {
		if (handle == configure.handle_list[i]) {
			return i;
		}
	}
	return -1;
}

bool handle_is_cccd(uint16_t handle)
{
	for (int i = 0; i < configure.cccd_num; i++) {
		if (get_idx_by_handle(handle) == configure.cccd_idx[i])
			return true;
	}
	return false;
}

struct gatts_uuid *get_char_uuid_by_char_handle(uint16_t handle)
{
	for (int i = 0; i < configure.char_num; i++) {
		if (configure.handle_list[configure.char_val_idx[i]] ==
		    handle) {
			return configure.char_uuid[i];
		}
	}
	return NULL;
}

struct gatts_uuid *get_char_uuid_by_cccd_handle(uint16_t handle)
{
	for (int i = 0; i < configure.cccd_num; i++) {
		if (configure.handle_list[configure.cccd_idx[i]] == handle) {
			return configure.char_cccd_uuid[i];
		}
	}
	return NULL;
}

struct gatts_uuid *get_char_uuid_by_handle(uint16_t handle)
{
	struct gatts_uuid *ret;
	ret = get_char_uuid_by_char_handle(handle);
	if (ret)
		return ret;

	ret = get_char_uuid_by_cccd_handle(handle);
	if (ret)
		return ret;
	return NULL;
}

bool get_char_handle_by_uuid(const struct gatts_uuid *uuid, uint16_t *handle)
{
	for (int i = 0; i < configure.char_num; i++) {
		if (equal_uuid(configure.char_uuid[i], uuid)) {
			*handle =
				configure.handle_list[configure.char_val_idx[i]];
			return true;
		}
	}
	return false;
}

bool get_cccd_handle_by_uuid(const struct gatts_uuid *uuid, uint16_t *handle)
{
	for (int i = 0; i < configure.cccd_num; i++) {
		if (equal_uuid(configure.char_cccd_uuid[i], uuid)) {
			*handle = configure.handle_list[configure.cccd_idx[i]];
			return true;
		}
	}
	return false;
}

// int get_cccd_idx_by_uuid(const struct gatts_uuid *uuid)
// {
// 	for (int i = 0; i < configure.cccd_num; i++) {
// 		if (equal_uuid(configure.char_cccd_uuid[i], uuid)) {
// 			return i;
// 		}
// 	}
// 	return -1;
// }

// DEBUG
void print_configure()
{
	printf("gatts_if: %u\n", configure.gatts_if);
	printf("conn_id: %u\n", configure.conn_id);
	printf("attr_num: %u\n", configure.attr_num);
	printf("service_num: %u\n", configure.service_num);
	printf("char_num: %u\n", configure.char_num);
	printf("cccd_num: %u\n", configure.cccd_num);

	printf("device_name: %s\n", configure.device_name);

	printf("*profile: %p\n", configure.profile);

	printf("*attr_db: %p\n", configure.attr_db);

	printf("*handle_list: %p\n", configure.handle_list);
	for (int i = 0; i < configure.attr_num; i++) {
		printf("        %u: %X\n", i, configure.handle_list[i]);
	}

	printf("*service_idx: %p\n", configure.service_idx);
	for (int i = 0; i < configure.service_num; i++) {
		printf("        %u: %u\n", i, configure.service_idx[i]);
	}

	printf("*char_val_idx: %p\n", configure.char_val_idx);
	for (int i = 0; i < configure.char_num; i++) {
		printf("        %u: %u\n", i, configure.char_val_idx[i]);
	}

	printf("*cccd_idx: %p\n", configure.cccd_idx);
	for (int i = 0; i < configure.cccd_num; i++) {
		printf("        %u: %u\n", i, configure.cccd_idx[i]);
	}

	printf("**char_cccd_uuid: %p\n", configure.char_cccd_uuid);
	for (int i = 0; i < configure.cccd_num; i++) {
		printf("        %u: %p -> %X\n", i, configure.char_cccd_uuid[i],
		       configure.char_cccd_uuid[i]->uuid.uuid16);
	}

	printf("**char_uuid: %p\n", configure.char_uuid);
	for (int i = 0; i < configure.char_num; i++) {
		printf("        %u: %p -> %X\n", i, configure.char_uuid[i],
		       configure.char_uuid[i]->uuid.uuid16);
	}
}
